﻿using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ThreeDWorld
{
    public class SpriteBatch : IDisposable
    {
        private AlphaTestEffect _Effect;
        private VertexPositionTexture[] _Vertices;
        private DynamicVertexBuffer _VertexBuffer;
        private IndexBuffer _IndexBuffer;
        private DepthStencilState _SavedDepthStencilState;
        private BlendState _SavedBlendState;
        private RasterizerState _SavedRasterizerState;
        private SamplerState _SavedSamplerState;
        private Texture2D _LastTexture;

        public SpriteBatch(Game game)
        {
            Game = game;

            _Effect = new AlphaTestEffect(GraphicsDevice);
            _Effect.VertexColorEnabled = false;
            _Effect.FogEnabled = false;

            _Vertices = new VertexPositionTexture[4];
            _VertexBuffer = new DynamicVertexBuffer(GraphicsDevice, typeof(VertexPositionTexture), 4, BufferUsage.WriteOnly);

            _IndexBuffer = new IndexBuffer(GraphicsDevice, IndexElementSize.SixteenBits, 6, BufferUsage.WriteOnly);
            _IndexBuffer.SetData<short>(new short[] { 0, 1, 2, 2, 3, 0 });
        }

        public Game Game
        {
            get;
            private set;
        }

        public GraphicsDevice GraphicsDevice
        {
            get { return Game.GraphicsDevice; }
        }

        public void Begin()
        {
            _SavedDepthStencilState = GraphicsDevice.DepthStencilState;
            _SavedBlendState = GraphicsDevice.BlendState;
            _SavedSamplerState = GraphicsDevice.SamplerStates[0];
            _SavedRasterizerState = GraphicsDevice.RasterizerState;

            GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
            GraphicsDevice.DepthStencilState = DepthStencilState.None;
            GraphicsDevice.BlendState = BlendState.AlphaBlend;
            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;

            _Effect.World = Matrix.Identity;
            _Effect.View = Matrix.Identity;
            _Effect.Projection = Matrix.CreateOrthographicOffCenter(0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 0, 1, -1);

            _LastTexture = null;
        }

        public void End()
        {
            GraphicsDevice.DepthStencilState = _SavedDepthStencilState;
            GraphicsDevice.BlendState = _SavedBlendState;
            GraphicsDevice.SamplerStates[0] = _SavedSamplerState;
            GraphicsDevice.RasterizerState = _SavedRasterizerState;
        }

        public void DrawText(SpriteFont font, string text, Vector2 position, Vector3 color, float alpha)
        {
            font.DrawText(this, text, position, color, alpha);
        }

        public void Draw(Texture2D texture, Vector2 position, Vector3 color, float alpha)
        {
            Draw(texture, position, new Vector2(1, 1), color, alpha);
        }

        public void Draw(Texture2D texture, Vector2 position, Vector2 scale, Vector3 color, float alpha)
        {
            Draw(texture, position, scale * new Vector2(texture.Width, texture.Height), new Rectangle(0, 0, texture.Width, texture.Height), color, alpha);
        }

        public void Draw(Texture2D texture, Vector2 position, Vector2 scale, Rectangle srcRect, Vector3 color, float alpha)
        {
            Vector2 s = new Vector2(texture.Width, texture.Height);

            if (_LastTexture != texture)
            {
                _Effect.Texture = texture;
                _LastTexture = texture;
            }
            _Effect.DiffuseColor = color;
            _Effect.Alpha = alpha;

            _Vertices[0].Position = new Vector3(position.X, position.Y, 0);
            _Vertices[1].Position = new Vector3(position.X + scale.X, position.Y, 0);
            _Vertices[2].Position = new Vector3(position.X + scale.X, position.Y + scale.Y, 0);
            _Vertices[3].Position = new Vector3(position.X, position.Y + scale.Y, 0);

            _Vertices[0].TextureCoordinate = new Vector2(srcRect.X, srcRect.Y) / s;
            _Vertices[1].TextureCoordinate = new Vector2(srcRect.X + srcRect.Width, srcRect.Y) / s;
            _Vertices[2].TextureCoordinate = new Vector2(srcRect.X + srcRect.Width, srcRect.Y + srcRect.Height) / s;
            _Vertices[3].TextureCoordinate = new Vector2(srcRect.X, srcRect.Y + srcRect.Height) / s;

            _VertexBuffer.SetData<VertexPositionTexture>(_Vertices);

            try
            {
                GraphicsDevice.SetVertexBuffer(_VertexBuffer);
                GraphicsDevice.Indices = _IndexBuffer;

                foreach (EffectPass pass in _Effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 4, 0, 2);
                }
            }
            catch (ObjectDisposedException)
            {
                End();
            }
            finally
            {
                GraphicsDevice.SetVertexBuffer(null);
                GraphicsDevice.Indices = null;
            }
        }

        public void Dispose()
        {
            if (!_VertexBuffer.IsDisposed)
            {
                _VertexBuffer.Dispose();
            }

            if (!_IndexBuffer.IsDisposed)
            {
                _IndexBuffer.Dispose();
            }
        }
    }
}
